﻿using Currency.Infra.Entities;
using Currency.Infra.EntityFramework;
using Currency.Infra.IRepository;
using Dapper;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace Currency.Infra.Repository
{
    /// <summary>
    /// 基础仓储
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public abstract class BaseRepository<TDbContext, TEntity> : IBaseRepository<TEntity>
        where TDbContext : DbContext
        where TEntity : Entity
    {
        protected virtual EfCoreDbContext DbContext { get; }

        protected virtual DbSet<TEntity> DbSet { get; }

        public BaseRepository(EfCoreDbContext dbContext)
        {
            DbContext = dbContext;
            DbSet = dbContext.Set<TEntity>();
        }

        //protected virtual IQueryable<TEntity> GetDbSet(bool writeDb, bool noTracking)
        //{
        //    if (noTracking && writeDb)
        //        return DbContext.Set<TEntity>().AsNoTracking().TagWith(EfCoreConsts.MAXSCALE_ROUTE_TO_MASTER);
        //    else if (noTracking)
        //        return DbContext.Set<TEntity>().AsNoTracking();
        //    else if (writeDb)
        //        return DbContext.Set<TEntity>().TagWith(EfCoreConsts.MAXSCALE_ROUTE_TO_MASTER);
        //    else
        //        return DbContext.Set<TEntity>();
        //}

        /// <summary>
        /// 插入单个实体
        /// </summary>
        /// <param name="entity"><see cref="TEntity"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns></returns>
        public virtual async Task<int> InsertAsync(TEntity entity, CancellationToken cancellationToken = default)
        {
            await DbSet.AddAsync(entity);
            return await DbContext.SaveChangesAsync(cancellationToken);
        }

        /// <summary>
        /// 批量插入实体
        /// </summary>
        /// <param name="entities"><see cref="TEntity"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns></returns>
        public virtual async Task<int> InsertRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
        {
            await DbSet.BulkInsertAsync(entities, cancellationToken);
            return 1;
        }

        /// <summary>
        /// 更新单个实体
        /// </summary>
        /// <param name="entity"><see cref="TEntity"/></param>
        /// <param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns></returns>
        public virtual async Task<int> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
        {
            //获取实体状态
            var entry = DbContext.Entry(entity);

            //如果实体没有被跟踪，必须指定需要更新的列
            if (entry.State == EntityState.Detached)
                throw new ArgumentException($"实体没有被跟踪，需要指定更新的列");

            //实体被标记为Added或者Deleted，抛出异常，ADNC应该不会出现这种状态。
            if (entry.State == EntityState.Added || entry.State == EntityState.Deleted)
                throw new ArgumentException($"{nameof(entity)},实体状态为{nameof(entry.State)}");

            return await DbContext.SaveChangesAsync(cancellationToken);
        }

        /// <summary>
        /// 根据条件查询实体是否存在
        /// </summary>
        /// <param name="whereExpression">查询条件</param>
        /// <param name="writeDb">是否读写库，默认false,可选参数</param>
        /// param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns></returns>
        public virtual async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> whereExpression, CancellationToken cancellationToken = default)
        {
            return await DbSet.AllAsync(whereExpression, cancellationToken);
        }

        /// <summary>
        /// 统计符合条件的实体数量
        /// </summary>
        /// <param name="whereExpression">查询条件</param>
        /// param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns></returns>
        public virtual async Task<int> CountAsync(Expression<Func<TEntity, bool>> whereExpression, CancellationToken cancellationToken = default)
        {
            return await DbSet.CountAsync(whereExpression, cancellationToken);
        }

        /// <summary>
        /// 根据条件查询，返回IQueryable<TEntity>
        /// </summary>
        /// <param name="expression">查询条件</param>
        /// <param name="writeDb">是否读写分离，默认false,可选参数</param>
        /// <param name="noTracking">是否开启跟踪，默认false,可选参数</param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> Where(Expression<Func<TEntity, bool>> expression, bool writeDb = false, bool noTracking = true)
        {
            return this.DbSet.Where(expression);
        }

        /// <summary>
        /// Dapper查询
        /// </summary>
        /// <param name="sql">sql</param>
        /// <param name="param">参数</param>
        /// <param name="commandTimeout">commandTimeout</param>
        /// <param name="commandType">commandType</param>
        public virtual async Task<IEnumerable<dynamic>> QueryAsync(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
        {
            var result = await DbContext.Database.GetDbConnection().QueryAsync(sql, param, null, commandTimeout, commandType);

            return result.Any() ? result : null;
        }

        /// <summary>
        /// Dapper查询
        /// </summary>
        /// <typeparam name="TResult"><see cref="TResult"/></typeparam>
        /// <param name="sql">sql</param>
        /// <param name="param">参数</param>
        /// <param name="commandTimeout">commandTimeout</param>
        /// <param name="commandType">commandType</param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<TResult>> QueryAsync<TResult>(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
        {
            var result = await DbContext.Database.GetDbConnection().QueryAsync<TResult>(sql, param, null, commandTimeout, commandType);

            return result.Any() ? result : null;
        }

        /// <summary>
        /// 分页方法
        /// </summary>
        /// <param name="pageNumber">第几页</param>
        /// <param name="pageSize">每页显示条数</param>
        /// <param name="whereExpression">查询条件</param>
        /// <param name="orderByExpression">排序条件</param>
        /// <param name="ascending">排序方式</param>
        /// param name="cancellationToken"><see cref="CancellationToken"/></param>
        /// <returns></returns>
        public virtual async Task<PageModel<TEntity>> PagedAsync(int pageIndex, int pageSize, Expression<Func<TEntity, bool>> whereExpression, Expression<Func<TEntity, object>> orderByExpression, bool ascending = false, CancellationToken cancellationToken = default)
        {
            var total = await this.DbSet.CountAsync(whereExpression, cancellationToken);
            if (total == 0)
                return new PageModel<TEntity>() { PageSize = pageSize };

            if (pageIndex <= 0)
                pageIndex = 1;

            if (pageSize <= 0)
                pageSize = 10;

            var query = this.DbSet.Where(whereExpression);
            query = ascending ? query.OrderBy(orderByExpression) : query.OrderByDescending(orderByExpression);
            var data = await EntityFrameworkQueryableExtensions.ToArrayAsync(
                                    query.Skip((pageIndex - 1) * pageSize).Take(pageSize)
                                   , cancellationToken);

            return new PageModel<TEntity>()
            {
                Page = pageIndex,
                PageSize = pageSize,
                PageCount = total,
                Data = data
            };
        }
    }
}
